#include "Labyrinth.h"
#include "RandomUtils.h"
#include "Assert.h"

const unsigned int Labyrinth::WIDTH;
const unsigned int Labyrinth::HEIGHT;

Labyrinth::Labyrinth():
	useExternalLabyrinth(false) {
}

//for tests only
Labyrinth::Labyrinth(const char labyrinth[Labyrinth::HEIGHT][Labyrinth::WIDTH]):
	useExternalLabyrinth(true) {
	for (unsigned int y = 0; y < HEIGHT; y++) {
		for (unsigned int x = 0; x < WIDTH; x++) {
			ASSERT(Cell(labyrinth[y][x]) == Cell::FREE ||
					Cell(labyrinth[y][x]) == Cell::OCCUPIED);
			this->labyrinth[y][x] = Cell(labyrinth[y][x]);
		}
	}
}

bool Labyrinth::isOnEdge(const Position & position) const {
	return position.getY() == 0 ||
		position.getX() == 0 ||
		position.getY() == HEIGHT - 1 ||
		position.getX() == WIDTH - 1;
}

bool Labyrinth::hasExit(Position position) {
	if (this->labyrinth[position.getY()][position.getX()] == Cell::FREE) {
		if (this->isOnEdge(position)) {
			return true;
		}

		this->labyrinth[position.getY()][position.getX()] = Cell::OCCUPIED; //mark as visited
		bool exitFound = this->hasExit(position.left()) ||
			this->hasExit(position.up()) ||
			this->hasExit(position.right()) ||
			this->hasExit(position.down());
		this->labyrinth[position.getY()][position.getX()] = Cell::FREE;
		return exitFound;
	}
	return false;
}

void Labyrinth::fillRandomly() {
	RandomUtils::updateSequance();
	const Cell cells[] = {
		Cell::FREE,
		Cell::OCCUPIED
	};
	for (unsigned int y = 0; y < HEIGHT; y++) {
		for (unsigned int x = 0; x < WIDTH; x++) {
			this->labyrinth[y][x] = RandomUtils::generateRandomObject<Cell>(cells, 2);
		}
	}
	Position centerPosition(Position::center(WIDTH, HEIGHT));
	this->labyrinth[centerPosition.getY()][centerPosition.getX()] = Cell::FREE;
}

//for tests only
void Labyrinth::clean() {
	for (unsigned int y = 0; y < HEIGHT; y++) {
		for (unsigned int x = 0; x < WIDTH; x++) {
			if (this->labyrinth[y][x] == Cell::PLAYER) {
				this->labyrinth[y][x] = Cell::FREE;
			}
		}
	}
}

void Labyrinth::rebuild() {
	if (this->useExternalLabyrinth) {
		this->clean();
	}
	else {
		do {
			this->fillRandomly();
		} while (!this->hasExit(Position::center(WIDTH, HEIGHT)));
	}
}

const Cell & Labyrinth::getCell(const Position & position) const throw (out_of_range) {
	if (static_cast<unsigned int> (position.getX()) < 0 || WIDTH <= static_cast<unsigned int> (position.getX()) ||
		static_cast<unsigned int> (position.getY()) < 0 || HEIGHT <= static_cast<unsigned int> (position.getY())) {
		throw (out_of_range("The position is out of range."));
	}
	return this->labyrinth[position.getY()][position.getX()];
}

void Labyrinth::setCell(const Position & position, const Cell & cell ) throw (out_of_range, invalid_argument) {
	if (static_cast<unsigned int> (position.getX()) < 0 || WIDTH <= static_cast<unsigned int> (position.getX()) ||
		static_cast<unsigned int> (position.getY()) < 0 || HEIGHT <= static_cast<unsigned int> (position.getY())) {
		throw (out_of_range("The position is out of range."));
	}
	if (cell != Cell::FREE && cell != Cell::OCCUPIED && cell != Cell::PLAYER) {
		throw (invalid_argument("Cells could be: free '-', occupied 'X' and player '*'."));
	}
	this->labyrinth[position.getY()][position.getX()] = cell;
}

ostream & operator <<(ostream & outputStream, const Labyrinth & labyrinth) {
	for (unsigned int y = 0; y < Labyrinth::HEIGHT; y++) {
		for (unsigned int x = 0; x < Labyrinth::WIDTH; x++) {
			outputStream << labyrinth.labyrinth[y][x] << " ";
		}
		outputStream << endl;
	}
	return outputStream;
}

